package services

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/deploy-services/v2/log"

	"github.com/deploy-services/v2/filesystem"

	"github.com/deploy-services/v2/kcfg"
	"github.com/google/uuid"
)

var NoNeedUpdate error = errors.New("NoNeedUpdate")
var NotFound error = errors.New("NotFound")

type histroy_info struct {
	Time    string
	Commit  string
	Results map[string]bool
}

type command struct {
	bind_env bool
	cmd      string
}

type service struct {
	name           string
	start_command  command
	stop_command   command
	health_command command
	nodes          []string
	depends        []string
	depends_path   *filesystem.Path
	status         []bool
	depends_files  filesystem.Files
	depends_ok     bool
	depends_active *filesystem.Path
	active_uuid    string
	server_nodes   nodes
	execute        *filesystem.Path
	history        []histroy_info
	pub            map[string][]string
	env            *environment
	workdir        string
	configure      *configure
	after_active   cmd_batch

	// commit 时每个节点同步成功失败的记录
	commit_results map[string]bool
	n              *kcfg.Node
}

func (s *service) show_history() string {
	var rest string
	rest += fmt.Sprintf("\n%v:\n", s.name)
	for i := len(s.history) - 1; i >= 0; i-- {
		if s.active_uuid == s.history[i].Commit {
			rest += fmt.Sprintf("\t%v\t%v\t*Active*\n", s.history[i].Time, s.history[i].Commit)
		} else {
			rest += fmt.Sprintf("\t%v\t%v\n", s.history[i].Time, s.history[i].Commit)
		}
	}
	return rest
}

func (s *service) run_cmd_node(cmd command, n *node) (string, error) {
	cmd_str := cmd.cmd
	if s.env != nil && cmd.bind_env {
		cmd_str, _ = n.generate_runcmd(s.name, s.workdir, s.env.envs, cmd.cmd)
	} else {
		cmd_str, _ = n.generate_runcmd(s.name, s.workdir, nil, cmd.cmd)
	}
	handle := n.dv_handle.CreateHandle()
	if handle == nil {
		return "", errors.New(n.name + " Create Handle Failed")
	}
	defer handle.Close()
	log.Println(s.name, n.name, "Execute:", cmd_str)
	return handle.Execute(cmd_str)
}

func (s *service) run_cmd(node string, cmd command) ([]string, []string, []error) {
	nodes_ret := []string{}
	nodes_err := []error{}
	nodes_name := []string{}
	if len(node) != 0 {
		for _, n := range s.server_nodes {
			if node == n.name {
				ret, err := s.run_cmd_node(cmd, n)
				nodes_ret = append(nodes_ret, ret)
				nodes_err = append(nodes_err, err)
				nodes_name = append(nodes_name, s.name+"#"+n.name)
				log.Logger().Info("S:%v N:%v Run <%s> <Res:%v><Err:%v>", s.name, n.name, cmd, ret, err)
				break
			}
		}
		return nodes_name, nodes_ret, nodes_err
	}
	for _, n := range s.server_nodes {
		ret, err := s.run_cmd_node(cmd, n)
		nodes_ret = append(nodes_ret, ret)
		nodes_err = append(nodes_err, err)
		nodes_name = append(nodes_name, s.name+"#"+n.name)
		log.Logger().Info("S:%v N:%v Run <Res:%v><Err:%v>  CMD:\n%s", s.name, n.name, ret, err, cmd.cmd)
	}
	return nodes_name, nodes_ret, nodes_err
}

func (s *service) service_start(node string) ([]string, []string, []error) {
	return s.run_cmd(node, s.start_command)
}

func (s *service) service_stop(node string) ([]string, []string, []error) {
	return s.run_cmd(node, s.stop_command)
}

func (s *service) service_health(node string) ([]string, []string, []error) {
	return s.run_cmd(node, s.health_command)
}

func (s *service) push(n *node, file string, content []byte) (string, error) {
	target_path := file
	if len(file) > 0 {
		if file[0] != filepath.Separator {
			target_path = filesystem.GetRemotePath(n.base, s.name, filepath.Join(s.workdir, file))
		}
	} else {
		target_path = filesystem.GetRemotePath(n.base, s.name, filepath.Join(s.workdir, "unknow"))
	}

	handle := n.dv_handle.CreateHandle()
	if handle == nil {
		return "", errors.New(n.name + "#" + n.name + " " + target_path + " Create Handle Failed")
	}
	defer handle.Close()
	handle.EnableFileTransport()
	err := handle.WriteFile(target_path, content)
	if err != nil {
		return "", errors.New(n.name + "#" + n.name + " " + target_path + " " + err.Error())
	}
	return "OK " + target_path, nil
}

func (s *service) service_push(node string, file string, content []byte) ([]string, []string, []error) {
	nodes_ret := []string{}
	nodes_err := []error{}
	nodes_name := []string{}
	if len(node) != 0 {
		for _, n := range s.server_nodes {
			if node == n.name {
				ret, err := s.push(n, file, content)
				nodes_ret = append(nodes_ret, ret)
				nodes_err = append(nodes_err, err)
				nodes_name = append(nodes_name, s.name+"#"+n.name)
				log.Logger().Info("S:%v N:%v Push <%s> <Res:%v><Err:%v>", s.name, n.name, file, ret, err)
				break
			}
		}
		return nodes_name, nodes_ret, nodes_err
	}
	for _, n := range s.server_nodes {
		ret, err := s.push(n, file, content)
		nodes_ret = append(nodes_ret, ret)
		nodes_err = append(nodes_err, err)
		nodes_name = append(nodes_name, s.name+"#"+n.name)
		log.Logger().Info("S:%v N:%v Push <Res:%v><Err:%v>", s.name, n.name, ret, err)
	}
	return nodes_name, nodes_ret, nodes_err
}

func (s *service) get_commit_file_key(n *node) string {
	return n.name + "@" + n.host + ":" + n.base
}

func (s *service) commit_files_to_all(commit_id string) error {
	var err error
	for _, file := range s.depends_files {
		if file.IsDir() {
			gz_file := file.Name + "." + commit_id + ".tar.gz"
			local_file := filesystem.GetLocalFilePath(file.Path())
			err = filesystem.TarGZ(gz_file, local_file)
			if err != nil {
				log.Println(s.name, "Tar file", local_file, err)
				return err
			}
			defer func(f string) {
				os.Remove(f)
			}(gz_file)
		}
	}
	for _, n := range s.server_nodes {
		s.commit_results[s.get_commit_file_key(n)] = false
		err = s.commit_files_to_node(commit_id, n)
		if err == NoNeedUpdate {
			s.commit_results[s.get_commit_file_key(n)] = true
			continue
		}
		if err != nil {
			break
		}
		s.commit_results[s.get_commit_file_key(n)] = true
	}
	return err
}

func (s *service) commit_files_to_node(commit_id string, n *node) error {
	handle := n.dv_handle.CreateHandle()
	if handle == nil {
		return errors.New("Connect " + n.host + " failed")
	}
	handle.EnableFileTransport()
	defer handle.Close()
	var err error
	var files filesystem.Files
	if s.depends_active == nil { // 本地没有 active 信息,全量更新
		files = s.depends_files
		if len(s.history) > 0 {
			latest := s.history[len(s.history)-1] // 如果当前节点已经标识 commit 完成，那么停止跟新
			if latest.Results != nil {
				if res, ok := latest.Results[s.get_commit_file_key(n)]; ok && res {
					// 不需要更新
					log.Logger().Debug("%v %v No Need Update Skip", s.name, n.host)
					return NoNeedUpdate
				}
			}
		}
	} else {
		// 取出远程的 active uuid，和本地 active 进行对比
		// 如果发现不一致那么进行全量更新
		// 否则进行增量更新
		// 如果增量更新列表为空，表示本地没有新增文件，不进行更新
		remote_active_file := filesystem.GetRemoteActivePath(n.base, s.name)
		active_commit_id, err2 := handle.ReadFile(remote_active_file)
		if err2 != nil {
			// 远程 active 读取失败，全量更新
			log.Println(s.name, "Read Active Failed,Full Update.", n.host, remote_active_file, err2)
			files = s.depends_files
		} else {
			if string(active_commit_id) != s.active_uuid {
				log.Println(s.name, "Commmit ID Not Same,Full Update.", n.host, string(active_commit_id), s.active_uuid)
				files = s.depends_files
			} else {
				// 计算 diff
				active_files := s.depends_active.GetFiles()
				diff_update := make(filesystem.Files, 0)
				for _, v1 := range s.depends_files {
					find := false
					if v1.IsDir() {
						// 目录需要强制更新
						// diff_update = append(diff_update, v1)
						continue
					}
					for _, v2 := range active_files {
						if v1.Path() == v2.Path() {
							if v1.MD5() == v2.MD5() {
								find = true
							} else {
								diff_update = append(diff_update, v1)
							}
						}
					}
					if !find {
						diff_update = append(diff_update, v1)
					}
				}

				if len(diff_update) == 0 {
					log.Println(s.name, n.host, "No Need Update, Skip")
					return NoNeedUpdate
				}

				files = s.depends_files
			}
		}
	}
	log.Println(s.name, "Update Count", len(files))
	if len(files) == 0 {
		log.Println(s.name, n.host, "No Need Update, Skip")
		return NoNeedUpdate
	}

	for _, file := range files {
		remote_file := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, file.Path())
		remote_dir := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, file.Dir())
		local_file := filesystem.GetLocalFilePath(file.Path())
		log.Println(s.name, local_file, "Upload to", n.host, remote_file)
		err = handle.Mkdir(remote_dir)
		if err != nil {
			log.Println(s.name, "Upload to", n.host, remote_file, err)
			break
		}
		if file.IsDir() {
			gz_file := file.Name + "." + commit_id + ".tar.gz"
			remote_dir = filesystem.GetRemoteFilePath(n.base, s.name, commit_id, file.Parent().Path())
			remote_gz_file := filepath.ToSlash(filepath.Join(remote_dir, gz_file))
			err = handle.PushFile(gz_file, remote_gz_file)
			if err != nil {
				log.Println(s.name, "Upload to", n.host, local_file, err)
				break
			}
			err = handle.UnTarGZ(remote_gz_file, remote_file)
			if err != nil {
				log.Println(s.name, "Upload to", n.host, local_file, err)
				break
			}

			err = handle.RmFile(remote_gz_file)
			if err != nil {
				log.Println(s.name, "Upload to", n.host, local_file, err)
				break
			}
		} else {
			// 先创建远端目录
			err = handle.PushFile(local_file, remote_file)
			if err != nil {
				log.Println(s.name, "Upload to", n.host, local_file, err)
				break
			}
		}
	}

	if err != nil {
		return err
	}
	// 给远程文件增加可执行权限
	remote_exefiles := s.execute.GetFiles()
	for _, file := range remote_exefiles {
		remote_file := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, file.Path())
		err = handle.AddExe(remote_file)
		if err != nil {
			log.Println(s.name, "Add EXE File to", n.host, remote_file, err)
			break
		}
	}
	if err != nil {
		return err
	}

	return nil
	// remote_active_file := filesystem.GetRemoteActivePath(n.base)
	// return handle.WriteFile(remote_active_file, commit_id)
}

func (s *service) active_node_check(commit_id string, n *node) error {
	//检查是否存在 commit id 版本
	handle := n.dv_handle.CreateHandle()
	if handle == nil {
		return errors.New("Connect " + n.host + " failed")
	}
	handle.EnableFileTransport()
	defer handle.Close()

	remote_service_path := filesystem.GetRemoteServicePath(n.base, s.name, commit_id)
	exist, err := handle.IsExist(remote_service_path)
	if err != nil {
		return err
	}

	if !exist {
		log.Println(s.name, "Active", commit_id, "Skip, Remote Not Found, Forgot Commit?")
		return NotFound
	}
	return nil
}

func (s *service) active_node_commit(commit_id string, n *node, force bool) error {
	// 更新版本,
	// 检查远端是否存在 ver 版本的目录，如果不存在那么跳过，并提示需要进行 commit
	// 读取远端 active 文件，是否和 ver 相同，如果相同就跳过节点更新
	handle := n.dv_handle.CreateHandle()
	if handle == nil {
		return errors.New("Connect " + n.host + " failed")
	}
	handle.EnableFileTransport()
	defer handle.Close()

	remote_active_file := filesystem.GetRemoteActivePath(n.base, s.name)
	active_commit_id, err := handle.ReadFile(remote_active_file)
	if err != nil {
		// 远程 active 文件当前不存在, 需要更新
		log.Println(s.name, "Active", commit_id, "Not Found Remote Active File")
	} else {
		// 对比 commit_id 是否相同，相同则跳过更新
		if !force && string(active_commit_id) == commit_id {
			log.Println(s.name, "Active", commit_id, "Same With Remote,Skip")
			return nil
		}
	}

	// 开始做 public 软连接
	for name, pubs := range s.pub {
		pub_handle := global_publics.get(name)
		for _, pub_path := range pubs {
			ln_to := pub_handle.get_remote_public_commit_path(n.base, pub_path)
			ln_from_parent := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, filesystem.GetParent(pub_path))
			handle.Mkdir(ln_from_parent)
			ln_from := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, pub_path)

			log.Println(s.name, "Active", commit_id, "Ln", ln_to, ln_from)
			err = handle.Ln(ln_to, ln_from)
			if err != nil {
				log.Println(s.name, "Active", commit_id, "Ln failed", err)
				return err
			}
		}
	}

	ln_from_dir := filesystem.GetRemotePath(n.base, s.name, "")
	handle.Mkdir(ln_from_dir)
	root_dirs := s.depends_path.GetRootDir()
	//开始创建软连接
	for _, file := range root_dirs {
		ln_to := filesystem.GetRemoteFilePath(n.base, s.name, commit_id, file.Path())
		ln_from := filesystem.GetRemotePath(n.base, s.name, file.Path())
		log.Println(s.name, "Active", commit_id, "Ln", ln_to, ln_from)
		err = handle.Ln(ln_to, ln_from)
		if err != nil {
			log.Println(s.name, "Active", commit_id, "Ln failed", err)
			return err
		}
	}
	// 开始替换结点内容

	if s.configure != nil {
		err = s.configure.replace(s, n, handle, commit_id)
		if err != nil {
			log.Println(s.name, "Active", commit_id, "Configure Replace failed.", err)
			return err
		}
		log.Logger().Info("%v %v %v Configure Replace Complate", s.name, commit_id, n.host)
	}

	// 需要执行 active 后的批处理命令
	err = s.after_active.run(s, n, handle)
	if err != nil {
		log.Println(s.name, n.name, commit_id, "After Active Run", "Failed.", err)
		return err
	}

	err = handle.WriteFile(remote_active_file, []byte(commit_id))
	if err != nil {
		log.Println(s.name, "Active", commit_id, "Write File failed", err)
		return err
	}
	log.Logger().Info("%v %v %v Active Complate", s.name, commit_id, n.host)
	return nil
}

func (s *service) active(ver string, force bool) (string, error) {
	if len(s.history) == 0 {
		log.Println(s.name, "No History Info,Skip")
		return "", errors.New("Not Found Any Commit")
	}
	if ver == "latest" {
		ver = s.history[len(s.history)-1].Commit
	} else {
		find := false
		for _, v := range s.history {
			if v.Commit == ver {
				find = true
				break
			}
		}
		if !find {
			log.Println(s.name, "Not Found", ver, ",Skip")
			return "", errors.New("Commit Not Found")
		}
	}

	if !force {
		// remote_commit_ok := true
		var err error
		for _, n := range s.server_nodes {
			err = s.active_node_check(ver, n)
			if err == NotFound || err != nil {
				log.Println(s.name, "Active", n.host, "Failed")
				break
			}
		}

		if err != nil {
			log.Println(s.name, "Active", ver, "Failed")
			return "", err
		}
	}

	for _, n := range s.server_nodes {
		err := s.active_node_commit(ver, n, force)
		if err != nil {
			log.Println(s.name, "Active", n.host, "Failed", err)
			return "", err
		}
	}
	s.active_uuid = ver
	ioutil.WriteFile(filesystem.GetActiveFilePath(s.name), []byte(ver), 0755)
	log.Logger().Info("%v Active %v Commpleted", s.name, ver)
	return s.active_uuid, nil
}

func (s *service) commit() (string, error) {
	// return s.depends_files
	s.commit_results = make(map[string]bool)
	commit_obj, _ := uuid.NewUUID()
	commit_id := commit_obj.String()
	if s.depends_active == nil { // 本地没有 active 信息,全量更新
		if len(s.history) > 0 { // 如果历史记录都没有，那么说明没有 commit过，进行全量跟新
			// 1. 取出最新的commit id
			// 2. 检查本地当前阶段是否成功更新，如果已经更新那么直接返回
			latest := s.history[len(s.history)-1]
			if latest.Results != nil {
				commit_id = latest.Commit
			}
		}
	}
	err := s.commit_files_to_all(commit_id)
	if err == NoNeedUpdate {
		return commit_id, err
	}
	if err == nil {
		// 开始保存
		filesystem.MkDirs(filesystem.GetServiceInfoPath(s.name))
		var rest [][]interface{} = make([][]interface{}, 0)
		for _, v := range s.depends_files {
			line := make([]interface{}, 3)
			line[0] = v.Path()
			line[1] = !v.IsDir()
			line[2] = v.MD5()
			rest = append(rest, line)
		}
		buff, _ := json.MarshalIndent(rest, "", "\t")
		ioutil.WriteFile(filesystem.GetDependInfoPath(s.name, commit_id), buff, 0755)

		// 保存历史记录并写入文件
		history := histroy_info{
			Time:    time.Now().Format("2006-01-02 15:04:05"),
			Commit:  commit_id,
			Results: s.commit_results,
		}
		if len(s.history) > 0 && s.history[len(s.history)-1].Commit == commit_id {
			s.history[len(s.history)-1] = history
		} else {
			s.history = append(s.history, history)
		}
		buff, _ = json.MarshalIndent(s.history, "", "\t")
		ioutil.WriteFile(filesystem.GetHistoryInfoPath(s.name), buff, 0755)
		log.Println(commit_id, "Commit Success")
		return commit_id, nil
	}

	return commit_id, err
}

func (s *service) updateNodes(m_nodes mnodes) {
	s.server_nodes = make(nodes, 0)
	for _, v := range s.nodes {
		ns, ok := m_nodes[v]
		if ok {
			s.server_nodes = append(s.server_nodes, ns...)
		}
	}
}

func (s *service) dump() string {
	var ss string
	ss += fmt.Sprintf("%v\n", s.name)
	ss += fmt.Sprintf("\tstart-command :%v\n", s.start_command.cmd)
	ss += fmt.Sprintf("\tstop-command  :%v\n", s.stop_command.cmd)
	ss += fmt.Sprintf("\thealth-command:%v\n", s.health_command.cmd)
	ss += fmt.Sprintf("\tnodes         :")
	for _, v := range s.nodes {
		ss += fmt.Sprintf("%v ", v)
	}
	ss += fmt.Sprintf("\n")
	ss += fmt.Sprintf("\tdepends ok    :%v\n", s.depends_ok)
	ss += fmt.Sprintf("\tdepends       :%v\n", len(s.depends_files))

	var bs strings.Builder
	w := tabwriter.NewWriter(&bs, 0, 0, 2, ' ', 0)
	fmt.Fprintf(w, "        \tFile\tMD5\n")
	fmt.Fprintf(w, "        \t----\t---\n")
	for _, v := range s.depends_files {
		fmt.Fprintf(w, "        \t%v\t%v\n", v.Path(), v.MD5())
	}
	w.Flush()
	ss += bs.String()
	return ss
}

func (s *service) getDepends() filesystem.Files {
	return s.depends_files
}

func (s *service) updateDependsFiles(path *filesystem.Path) {
	s.depends_files = make(filesystem.Files, 0)
	set := NewSet()
	s.depends_ok = true
	s.depends_path = filesystem.NewPath()
	for _, p := range s.depends {
		log.Logger().Debug("S:%v Check Depend Path %v", s.name, p)
		s.depends_path.Add(p)
	}

	files := path.GetFiles()
	for _, f := range files {
		find := false
		for _, p := range s.depends {
			// log.Logger().Debug("S:%v Check %v With %v", s.name, f.Path(), p)
			if f.IsBelongTo(p) {
				log.Logger().Debug("S:%v Check %v With %v, Is Yes", s.name, f.Path(), p)
				find = true
				break
			}
		}
		if find && !set.Has(f.Path()) {
			log.Logger().Debug("S:%v %v IsDir:%v HasChild:%v", s.name, f.Path(), f.IsDir(), f.HasChild())
			if f.IsDir() && !f.HasChild() {
				set.Insert(f.Path())
				s.depends_files = append(s.depends_files, f)
			} else if !f.IsDir() {
				set.Insert(f.Path())
				s.depends_files = append(s.depends_files, f)
			}
		}
	}
	log.Println(s.name, "Depends Fiels Count:", len(s.depends_files))
	s.history = make([]histroy_info, 0)
	// 加载历史记录
	history_file, err := ioutil.ReadFile(filesystem.GetHistoryInfoPath(s.name))
	if err == nil {
		err = json.Unmarshal(history_file, &s.history)
		if err != nil {
			log.Println(filesystem.GetHistoryInfoPath(s.name), err)
		}
	} else {
		log.Println(filesystem.GetHistoryInfoPath(s.name), err)
	}
}

// 先读取 ./deploy/$service/active 文件内容获取到活跃uuid
// 在读取 ./deploy/$service/$uuid 文件内容，获取到依赖文件和目录列表，以及文件的MD5值
func (s *service) loadActiveDepends() {
	uuid, err := ioutil.ReadFile(filesystem.GetActiveFilePath(s.name))
	if err != nil {
		log.Println(filesystem.GetActiveFilePath(s.name), err)
		return
	}

	// 读取到uuid,继续读取依赖文件内容
	content, err := ioutil.ReadFile(filesystem.GetDependInfoPath(s.name, string(uuid)))
	if err != nil {
		log.Println(filesystem.GetDependInfoPath(s.name, string(uuid)), err)
		return
	}
	// 文件内容
	// 文件名，是否文件，MD5
	// 例如  /fpbx/test,true, md5 string
	var rest [][]interface{}
	err = json.Unmarshal(content, &rest)
	if err != nil {
		log.Println(filesystem.GetDependInfoPath(s.name, string(uuid)), err)
		return
	}

	if len(rest) == 0 {
		log.Println(filesystem.GetDependInfoPath(s.name, string(uuid)), "Empty Active File")
		return
	}

	s.depends_active = filesystem.NewPath()
	for _, v1 := range rest {
		if len(v1) != 3 {
			log.Println(filesystem.GetDependInfoPath(s.name, string(uuid)), "Format Error")
			continue
		}
		log.Println("Service:", s.name, "Add File", v1[0], v1[1], v1[2])
		s.depends_active.AddFile2(v1[0].(string), v1[1].(bool), v1[2].(string))
	}
	s.active_uuid = string(uuid)
	return
}

type services map[string]*service

func (s *services) dump(name string) string {
	var rest string
	if len(name) == 0 {
		for _, v := range *s {
			rest += v.dump()
		}
	} else {
		for _, v := range *s {
			if v.name == name {
				rest += v.dump()
			}
		}
	}
	return rest
}

func (s *services) getAllDepends() filesystem.Files {
	set := NewSet()
	var result filesystem.Files = make(filesystem.Files, 0)
	for _, v := range *s {
		depends := v.getDepends()
		for _, n := range depends {
			if !set.Has(n.Path()) {
				set.Insert(n.Path())
				result = append(result, n)
			}
		}
	}
	return result
}

func (s *services) getAllActiveDepends() filesystem.Files {
	set := NewSet()
	var result filesystem.Files = make(filesystem.Files, 0)
	for _, v := range *s {
		if v.depends_active == nil {
			continue
		}
		depends := v.depends_active.GetFiles()
		for _, n := range depends {
			if !set.Has(n.Path()) {
				set.Insert(n.Path())
				result = append(result, n)
			}
		}
	}
	return result
}

func (s *services) updateDependsFiles(path *filesystem.Path) {
	for _, v := range *s {
		v.updateDependsFiles(path)
	}
}

func (s *services) loadActiveDepends() {
	for _, v := range *s {
		v.loadActiveDepends()
	}
}

func (s *services) commit(name string) (names []string, rests []string, errs []error) {
	if len(name) == 0 {
		for _, v := range *s {
			rest, err := v.commit()
			names = append(names, v.name)
			rests = append(rests, rest)
			errs = append(errs, err)
		}
	} else {
		for _, v := range *s {
			if v.name == name {
				rest, err := v.commit()
				names = append(names, v.name)
				rests = append(rests, rest)
				errs = append(errs, err)
			}
		}
	}
	// return rests, errs
	return names, rests, errs
}

func (s *services) active(name string, commit string, force bool) (names []string, rests []string, errs []error) {
	if len(name) == 0 {
		for _, v := range *s {
			rest, err := v.active(commit, force)
			names = append(names, v.name)
			rests = append(rests, rest)
			errs = append(errs, err)
		}
	} else {
		for _, v := range *s {
			if v.name == name {
				rest, err := v.active(commit, force)
				names = append(names, v.name)
				rests = append(rests, rest)
				errs = append(errs, err)
			}
		}
	}
	// return rests, errs
	return names, rests, errs
}

// func (s *services) updateNodes(nodes mnodes) {
// 	for _, v := range *s {
// 		v.updateNodes(nodes)
// 	}
// }

func (s *services) show_history(service string) string {
	var rest string
	if len(service) == 0 {
		for _, v := range *s {
			rest += v.show_history()
		}
	} else {
		for _, v := range *s {
			if v.name == service {
				rest += v.show_history()
			}
		}
	}
	return rest
}

func (s *services) run_cmd(service string, node string, cmd command) ([]string, []string, []error) {
	for _, v := range *s {
		if len(service) == 0 || v.name == service {
			return v.run_cmd(node, cmd)
		}
	}

	return []string{}, []string{}, []error{}
}

func (s *services) service_start(service string, node string) (names []string, rests []string, errs []error) {
	if len(service) == 0 {
		for _, v := range *s {
			ns, rest, err := v.service_start(node)
			names = append(names, ns...)
			rests = append(rests, rest...)
			errs = append(errs, err...)
		}
	} else {
		for _, v := range *s {
			if v.name == service {
				ns, rest, err := v.service_start(node)
				names = append(names, ns...)
				rests = append(rests, rest...)
				errs = append(errs, err...)
			}
		}
	}
	return names, rests, errs
}

func (s *services) service_stop(service string, node string) (names []string, rests []string, errs []error) {
	if len(service) == 0 {
		for _, v := range *s {
			ns, rest, err := v.service_stop(node)
			names = append(names, ns...)
			rests = append(rests, rest...)
			errs = append(errs, err...)
		}
	} else {
		for _, v := range *s {
			if v.name == service {
				ns, rest, err := v.service_stop(node)
				names = append(names, ns...)
				rests = append(rests, rest...)
				errs = append(errs, err...)
			}
		}
	}
	return names, rests, errs
}

func (s *services) service_health(service string, node string) (names []string, rests []string, errs []error) {
	if len(service) == 0 {
		for _, v := range *s {
			ns, rest, err := v.service_health(node)
			names = append(names, ns...)
			rests = append(rests, rest...)
			errs = append(errs, err...)
		}
	} else {
		for _, v := range *s {
			if v.name == service {
				ns, rest, err := v.service_health(node)
				names = append(names, ns...)
				rests = append(rests, rest...)
				errs = append(errs, err...)
			}
		}
	}
	return names, rests, errs
}

func (s *services) service_push(service string, node string, file string, content []byte) (names []string, rests []string, errs []error) {
	if len(service) == 0 {
		for _, v := range *s {
			ns, rest, err := v.service_push(node, file, content)
			names = append(names, ns...)
			rests = append(rests, rest...)
			errs = append(errs, err...)
		}
	} else {
		for _, v := range *s {
			if v.name == service {
				ns, rest, err := v.service_push(node, file, content)
				names = append(names, ns...)
				rests = append(rests, rest...)
				errs = append(errs, err...)
			}
		}
	}
	return names, rests, errs
}

func parseService(n *kcfg.Node) *service {
	sv := &service{
		nodes:   make([]string, 0),
		depends: make([]string, 0),
		execute: filesystem.NewPath(),
		pub:     make(map[string][]string),
		n:       n,
	}
	sv.name = n.Child("name").GetString()
	cmd := n.Child("start_command")
	if cmd.IsString() {
		sv.start_command = command{
			bind_env: false,
			cmd:      cmd.GetString(),
		}
	} else {
		sv.start_command = command{
			bind_env: cmd.Child("bind_env").GetBool(),
			cmd:      cmd.Child("cmd").GetString(),
		}
	}

	cmd = n.Child("stop_command")
	if cmd.IsString() {
		sv.stop_command = command{
			bind_env: false,
			cmd:      cmd.GetString(),
		}
	} else {
		sv.stop_command = command{
			bind_env: cmd.Child("bind_env").GetBool(),
			cmd:      cmd.Child("cmd").GetString(),
		}
	}

	cmd = n.Child("health_command")
	if cmd.IsString() {
		sv.health_command = command{
			bind_env: false,
			cmd:      cmd.GetString(),
		}
	} else {
		sv.health_command = command{
			bind_env: cmd.Child("bind_env").GetBool(),
			cmd:      cmd.Child("cmd").GetString(),
		}
	}

	nodes, _ := n.Child("nodes").ChildsAllWithoutNote()
	depends, _ := n.Child("depends").ChildsAllWithoutNote()
	for _, v := range nodes {
		sv.nodes = append(sv.nodes, v)
	}

	for _, v := range depends {
		sv.depends = append(sv.depends, v)
	}

	execute, _ := n.Child("execute").ChildsAllWithoutNote()
	for _, v := range execute {
		sv.execute.Add(v)
	}

	pub := n.Child("public")
	pub_keys, pub_nodes := pub.ChildsAllWithoutNote()
	for i, name := range pub_keys {
		// 先校验 public name 是否存在
		var find_pub *public
		for _, pub_handle := range global_publics {
			if pub_handle.name == name {
				find_pub = pub_handle
				break
			}
		}
		if find_pub == nil {
			log.Panicln(sv.name, "Parse Public", name, "Failed")
			return nil
		}
		// 校验 public depends 是否存在
		pub_path := pub_nodes[i].GetString()
		if !find_pub.IsExistPath(pub_path) {
			log.Panicln(sv.name, "Parse Public", pub_path, "Failed")
		}

		if _, ok := sv.pub[name]; !ok {
			sv.pub[name] = []string{}
		}
		sv.pub[name] = append(sv.pub[name], pub_path)
	}

	env := n.Child("environment").GetString()
	if len(env) > 0 {
		sv.env, _ = global_environments[env]
	}

	conf := n.Child("configure").GetString()
	if len(conf) > 0 {
		sv.configure, _ = global_configures[conf]
	}

	sv.workdir = n.Child("workdir").GetString()
	sv.loadActiveDepends()
	sv.after_active = cmd_batch{}
	sv.after_active.parse(n.Child("after_active"))
	sv.updateNodes(global_mnodes)
	global_daemon_manager.register(sv)
	return sv
}

func parseServices(n []*kcfg.Node) services {
	rest := make(services)
	for _, nn := range n {
		sv := parseService(nn)
		rest[sv.name] = sv
	}
	return rest
}
